package parser;

/*
 * This program uses the Matra DTD Parser library and Wutka's one to parse the content of elements
 */
import java.io.*;
import java.util.*;

import ui.EDC;
import utils.EDCResource;

import com.conradroche.matra.exception.*;
import com.conradroche.matra.decl.*;
import com.wutka.dtd.*;

/**
 * This class checks the DTD files in order to tell retrieve
 * some statistics (how many are ill-formed and what element(s)
 * are responsible for that)
 *
 */

public class Parser {
	private int currentDTDIndex = 0; // If there is only one DTD to analyze, this variable won't change but it is necessary to initialize it with 0
	/*
	 * Number of rows for properties (minus one which counts bad lines per file)
	 * Change this value to add new properties
	 */
	public int nbStatsCols = 6;
	public ArrayList<Integer> badDTDs;
	public ArrayList<String> dtdNames;
	public Logger l = EDCResource.logger;
	// temporary variable to parse some elements
	private com.wutka.dtd.DTD wutkaResult;
	
	private StringBuffer sBuffer;
	/*
	 * stats: double entry array which contains statistics about the violations to the rules 
	 * Lines: dtd files
	 * Rows: Property to check (plus one row for number of bad lines)
	 */
	public int[][] stats;
	// stats[] = { TotalBadLines, ANY, EMPTY, Redundancy, StarredChoiceElement, UnstarredChoice }
	public String[] errorMsgs = new String[]{"bad line(s)", "ANY(s)", "EMPTY(s)", "redudancy(ies)", "starred choice element(s)", "unstarred choice(s)"};
	public int totalBadFiles = 0;
	public int totalParsedFiles=0;

	/**
	 * ParseDTDFile Constructor.
	 */
	public Parser() {
		super();
		EDCResource.checkConfig();
	}
	/**
	 * This method checks whether the DTD fileName requires the desired properties
	 * @param fileName
	 */
	public  void checkDTD(File f)
	{
		String fileName=f.getName();
		//l.addNewFileEntry(fileName);
		boolean isOK=true;
		try {
			// Matra parser
			com.conradroche.matra.dtdparser.DTDParser parser = new com.conradroche.matra.dtdparser.DTDParser();
			try {
				// Wutka's parser
				com.wutka.dtd.DTDParser wutkaParser = new com.wutka.dtd.DTDParser(f);
				wutkaResult = wutkaParser.parse();
			}
			catch (Exception e) {}
			if (EDCResource.verbose)
				System.out.println("=========== Parsing DTD  " + fileName + " ===============");
			
			Date t1=new Date();
			String dtdString="";
			try {
				BufferedReader bf=new BufferedReader(new FileReader(f));
				String line;

				while ((line=bf.readLine())!=null)
					dtdString+=line;
				
			} catch (IOException e) {
				l.addException(e.getMessage());
			}
			
			parser.parse(dtdString);
			Date t2=new Date();
			long res=t2.getTime()-t1.getTime();
			
			if (EDCResource.verbose)
				System.out.println("DTD was parsed in "+res+" miliseconds");
			l.addInfo("ParsingTime", String.valueOf(res));

			DocType doc=parser.getDocType();
			Hashtable h=doc.getElementList();
			Enumeration e=h.elements();
			int totalElements=h.size();
			int elementsDone=0;
			while (e.hasMoreElements()) {
				if (EDCResource.verbose)
					System.out.println("Analyzing "+fileName+", "+elementsDone+"/"+totalElements+" done...");
				ElementType t=(ElementType)e.nextElement();
				if (!checkElement(t)) { 
					isOK=false;
					stats[currentDTDIndex][0]++;
				}
				elementsDone++;
			}
			if (!isOK) {
				badDTDs.add(Integer.valueOf(currentDTDIndex));
				totalBadFiles++;
			} 
			totalParsedFiles++; // If this line is reached, that means the DTD was fully parsed without any exception
		} catch(DTDException dtdEx) {
			if (EDCResource.verbose)
				System.out.println("A DTDException occured while parsing the file - ");
			l.addException(dtdEx.getMessage());
			for (int k=0; k < nbStatsCols; k++)
				stats[currentDTDIndex][k]=-1;
		}
		//l.closeFileEntry();
	}

	/**
	 * Checks the ElementType t with the desired properties
	 * @param t The current ElementType
	 */
	public  boolean checkElement(ElementType t) {
		boolean isOK=true;
		// Try to check basic properties using matra parser
		if (t.isAnyContentModel() && EDCResource.checkAny) { // ANY
			isOK = false;
			stats[currentDTDIndex][1]++;
			l.addViolation(errorMsgs[1],t.getName());
		}
		else if (t.isEmptyContentModel() && EDCResource.checkEmpty) { // EMPTY
			isOK = false;
			stats[currentDTDIndex][2]++;
			l.addViolation(errorMsgs[2],t.getName());
		}

		else { // try to parse the element contents with wutka's parser
			try {
				Vector<String> names=new Vector<String>();

				Hashtable h=wutkaResult.elements;
				DTDElement element=(DTDElement)h.get(t.getName());
				if (!checkDTDItem(element.content,names)) {
					isOK=false;
				}
			}
			catch(Exception e) {
//				e.printStackTrace();
				if (EDCResource.verbose) {
					System.out.println("wutka's parser has failed");
					System.out.println("Results could be false for this dtd (or bad lines too few)");
				}
				l.addException(e.getMessage());
			}
		}
		// You can check properties on the attributes adding requested code here (using either wutka's or matra's parser)
		//AttList att=t.getAttList();
		//AttList att=new AttList();
		//Vector v=att.getAttribs();
		//for (int i=0; i< v.size(); i++)
		// {
		//     checkAttrib(v.elementAt(i));
		//	}
		// 
		return isOK;
	}

	/**
	 * Check an item with wutka's parser (alternate method)
	 * @param item The item to check
	 * @param elements A vector to store the names in the whole element contents, to check redundancy
	 * @return a boolean that tells whether the item requires some properties
	 */
	private  boolean checkDTDItem(DTDItem item, Vector<String> elements) {
		return checkDTDItem(item, elements, false);
	}

	/**
	 * Check an item with wutka's parser (alternate method)
	 * @param item The item to check
	 * @param elements A vector to store the names in the whole element contents, to check redundancy
	 * @param isInChoice context variable to indicate if the item is inside a choice 
	 * @return a boolean that tells whether the item requires some properties
	 */
	private  boolean checkDTDItem(DTDItem item, Vector<String> elements, boolean isInChoice) {
		boolean isOK=true;
		boolean isChoice = false;
		DTDItem[] items = null;
		
		if (item instanceof DTDName) {
			String name = ((DTDName) item).value;
			if (elements.contains(name) && EDCResource.checkRedundancy) {
				isOK = false;
				stats[currentDTDIndex][3]++;
				l.addViolation(errorMsgs[3],name);
			}
			else 
				elements.add(name);
		}
		else if (item instanceof DTDChoice) {
			isChoice = true;
			items = ((DTDChoice) item).getItems();	
		}
		else if (item instanceof DTDSequence) {
			items = ((DTDSequence) item).getItems();
		}
		else if (item instanceof DTDMixed) {
			items = ((DTDMixed) item).getItems();
		}
		else if (item instanceof DTDPCData) {}

		if (item.cardinal == DTDCardinal.OPTIONAL) {
			if (isChoice && EDCResource.checkUnStarredChoice) {
				isOK = false;
				stats[currentDTDIndex][5]++;
				l.addViolation(errorMsgs[5],item.toString());
			}
		}
		else if (item.cardinal == DTDCardinal.ZEROMANY) {
			if (isInChoice && EDCResource.checkStarredChoiceElement) {
				isOK = false;
				stats[currentDTDIndex][4]++;
				l.addViolation(errorMsgs[4],item.toString());
			}
		}
		else if (item.cardinal == DTDCardinal.ONEMANY) {
			if (isInChoice && EDCResource.checkStarredChoiceElement) {
				isOK = false;
				stats[currentDTDIndex][4]++;
				l.addViolation(errorMsgs[4],item.toString());
			}
		} else {
			// There is no cardinal
			if (isChoice && EDCResource.checkUnStarredChoice) {
				isOK = false;
				stats[currentDTDIndex][5]++;
				l.addViolation(errorMsgs[5],item.toString());
			}
		}
		
		// if there are embedded items and the current item is not tagged as "bad" check the sub-items
		if (items != null && isOK) {
			for (int i=0; i < items.length; i++) {
				isOK = isOK && checkDTDItem(items[i], elements, true);
			}
		}
		return isOK;
	}

	/**
	 * Parse the DTDs found in the specified dtds list and calculates the statistics
	 */
	public void parseDTDs() {
		sBuffer = new StringBuffer();
		
		try {
			System.out.println("Checking the DTDs in "+EDCResource.inputFile);
			sBuffer.append("Checking the DTDs in "+EDCResource.inputFile);
			sBuffer.append("\n");
			
			File[] dtds = EDCResource.dtds;

			dtdNames = new ArrayList<String>(dtds.length);
			badDTDs = new ArrayList<Integer>(dtds.length/2);
			stats = new int[dtds.length][nbStatsCols];

			// initialization of the stats array
			for (int i=0; i<stats.length; i++) {
				for (int j=0; j<stats[i].length; j++) {
					stats[i][j] = 0;
				}
			}

			String name;

			Thread th = new Thread();
			th.start();

			for (int j=0; j<dtds.length; j++) {
				int index = j+1;
				Double val = new Double((index/(float)dtds.length)*100);
				if (EDCResource.verbose) {
					System.out.println("Size : "+dtds.length);
					System.out.println("Index : "+index);
					System.out.println("val : "+val.doubleValue());
					System.out.println("Value2 : "+((float)((j+1)/dtds.length)*100));
					
					sBuffer.append("Size : "+dtds.length);
					sBuffer.append("\n");
					sBuffer.append("Index : "+index);
					sBuffer.append("\n");
					sBuffer.append("val : "+val.doubleValue());
					sBuffer.append("\n");
					sBuffer.append("Value2 : "+((float)((j+1)/dtds.length)*100));
					sBuffer.append("\n");
				}

				if (!EDCResource.console) {
					EDC.getCurrent().getStatusPane().getProgressBar().setValue(val.intValue());
					EDC.getCurrent().getStatusPane().updateUI();
				}
				th.sleep(100);

				currentDTDIndex = j;
				name = dtds[j].getName();
				dtdNames.add(name);

				if (EDCResource.verbose) {
					System.out.println(name);
					sBuffer.append(name);
					sBuffer.append("\n");
				}
				l.addNewFileEntry(name);
				try {
					checkDTD(dtds[j]);
				} catch (Exception e) {
					if (EDCResource.verbose) {
						System.out.println("Warning: problems while parsing "+name);
						sBuffer.append("Warning: problems while parsing "+name);
						sBuffer.append("\n");
					}
					l.addException(e.getMessage());
					for (int k=0; k < nbStatsCols; k++)
						stats[currentDTDIndex][k]=-1;
				}
				l.closeFileEntry();
			}
			l.closeAll();
			l.flush();
			// Display bad files
			if (totalParsedFiles > 0)
			{
				System.out.println("There are "+totalBadFiles
						+" wrong DTDs (out of "+totalParsedFiles+" => "
						+(float)(totalBadFiles*100/totalParsedFiles)+"% bad):");
				System.out.println("Note that "+(dtds.length-totalParsedFiles)+" DTD could not be parsed.");
				System.out.println("DTDs that do not respect at least one property are the following ones:");
				
				sBuffer.append("There are "+totalBadFiles
						+" wrong DTDs (out of "+totalParsedFiles+" => "
						+(float)(totalBadFiles*100/totalParsedFiles)+"% bad):");
				sBuffer.append("\n");
				sBuffer.append("Note that "+(dtds.length-totalParsedFiles)+" DTD could not be parsed.");
				sBuffer.append("\n");
				sBuffer.append("DTDs that do not respect at least one property are the following ones:");
				sBuffer.append("\n");
			}
			else {
				System.out.println("Sorry, there was a problem while parsing all the DTDs, none was parsed");
				sBuffer.append("Sorry, there was a problem while parsing all the DTDs, none was parsed");
				sBuffer.append("\n");
			}
			
			System.out.print("| "+errorMsgs[0]);
			sBuffer.append("| "+errorMsgs[0]);
			if (EDCResource.checkAny) {
				System.out.print(" | "+errorMsgs[1]);
				sBuffer.append(" | "+errorMsgs[1]);
			}
			if (EDCResource.checkEmpty) {
				System.out.print(" | "+errorMsgs[2]);
				sBuffer.append(" | "+errorMsgs[2]);
			}
			if (EDCResource.checkRedundancy) {
				System.out.print(" | "+errorMsgs[3]);
				sBuffer.append(" | "+errorMsgs[3]);
			}
			if (EDCResource.checkStarredChoiceElement) {
				System.out.print(" | "+errorMsgs[4]);
				sBuffer.append(" | "+errorMsgs[4]);
			}
			if (EDCResource.checkUnStarredChoice) {
				System.out.print(" | "+errorMsgs[5]);
				sBuffer.append(" | "+errorMsgs[5]);
			}
			System.out.println(" | file");
			sBuffer.append(" | file");
			sBuffer.append("\n");
			
			int i;
			for (Integer index : badDTDs) {
				i = index.intValue();
				System.out.print("| "+centerContentText(stats[i][0], errorMsgs[0]));
				sBuffer.append("| "+centerContentText(stats[i][0], errorMsgs[0]));
				
				if (EDCResource.checkAny) {
					System.out.print(" | "+centerContentText(stats[i][1], errorMsgs[1]));
					sBuffer.append(" | "+centerContentText(stats[i][1], errorMsgs[1]));
				}
				if (EDCResource.checkEmpty) {
					System.out.print(" | "+centerContentText(stats[i][2], errorMsgs[2]));
					sBuffer.append(" | "+centerContentText(stats[i][2], errorMsgs[2]));
				}
				if (EDCResource.checkRedundancy) {
					System.out.print(" | "+centerContentText(stats[i][3], errorMsgs[3]));
					sBuffer.append(" | "+centerContentText(stats[i][3], errorMsgs[3]));
				}
				if (EDCResource.checkStarredChoiceElement) {
					System.out.print(" | "+centerContentText(stats[i][4], errorMsgs[4]));
					sBuffer.append(" | "+centerContentText(stats[i][4], errorMsgs[4]));
				}
				if (EDCResource.checkUnStarredChoice) {
					System.out.print(" | "+centerContentText(stats[i][5], errorMsgs[5]));
					sBuffer.append(" | "+centerContentText(stats[i][5], errorMsgs[5]));
				}
				System.out.println(" | "+dtdNames.get(i));
				sBuffer.append(" | "+dtdNames.get(i));
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public String centerContentText(int content, String header) {
		return centerContentText(String.valueOf(content), header);
	}
	
	public String centerContentText(String content, String header) {
		int length = header.length() - content.length();
		int lead = length/2 + length%2;
		int trail = length / 2;
		
		String res = "";
		
		for (int i=0; i<lead; i++)
			res = res.concat(" ");
		res = res.concat(content);
		for (int i=0; i<trail; i++)
			res = res.concat(" ");
		
		return res;
	}
	/**
	 * @return the sBuffer
	 */
	public StringBuffer getSBuffer() {
		return sBuffer;
	}
}
